package pl.edu.pw.elka.tinyrepo.common;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashMap;
import java.util.Map;
import java.util.StringTokenizer;



// FIXME poprawic zaleznosc z "podgladaniem bufora" w celu znalezienia rodzaju naglowka wykorzystujac PushbackInputStream

/**
 * Klasa realizujaca serwer webDAVa.
 * Klasa jest odpowiedzialna jedynie za przyjmowanie
 * requestow, przekazanie requesta do odpowiedniego handlera,
 * pobranie response od serwera a nastepnie przeslanie response
 * do klienta.
 *  
 * @author Piotr Jarosik
 */
public abstract class DAVServer {
   /** Przechowuje PROTOTYPY handlerow requestow. */
   private Map<String, RequestHandler> requestMap = 
      new HashMap<String, RequestHandler>();
   /** Port na ktorym bedzie uruchomiony serwer. */
   private Integer port;
   
   /**
    * Konstruktor do tworzenia serwera na okreslonym porcie. 
    * 
    * @param port
    */
   public DAVServer(final Integer port) {
      this.port = port;
   }
   
   /**
    * Glowna petla serwera.
    */
   public void start() {
      ServerSocket s = null;
      try {
         s = new ServerSocket(port); // otwarcie nowego portu
         for(;;) { // petelka
            Socket incomming = s.accept(); // socket do klienta (oczekiwanie)
            System.out.println("New connection: "+incomming);
            Thread t = new Thread(new RequestHandlerThread(incomming));
            t.start();// przekazanie obslugi do nowego watku
         }
            
      }
      catch (Exception e) { // FIXME rozdzielic na catch dla UnknownHostException ...
         System.out.println("SRV ERROR: "+e);
         e.printStackTrace();
         try {
            s.close();
         } catch (IOException e1) {
            // TODO Auto-generated catch block
            e1.printStackTrace();
         }
         System.exit(-1);
      }
   }
   
   public int getPort() {
      return port;
   }
   
   /**
    * Dodaje handler dla okreslonego rodzaju request'a. 
    * Gdy handler dla danego typu request'a juz istnial,
    * wartosc jest podmieniana.
    * 
    * @param rqClass
    * @param rqHandler
    */
   protected void addHandler(String rqString, RequestHandler rqHandler) {
      requestMap.put(rqString, rqHandler);
   }
   
   
   
   /**
    * Nowy watek dla odebranego polaczenia.
    * Klasa ta tylko wstepnie okresla handlera i ew. wysyla info
    * z bledem. Reszta dzialan dokonywana jest przez odpowiedniego handlera.
    * 
    * @author Piotr Jarosik
    */
   class RequestHandlerThread implements Runnable {
      /** socket. */
      private Socket incomming;
      
      /**
       * Konstruktor.
       */
      public RequestHandlerThread(Socket incomming) {
         this.incomming = incomming;
      }
      
      @Override
      public void run() {
         
         String requestType = null;
         String requestLine = null;
         
         try {
            InputStream in = incomming.getInputStream();
            OutputStream out = incomming.getOutputStream();
            requestLine = Message.readInputLine(in);
            requestType = getRequestType(requestLine);
            RequestHandler rh = requestMap.get(requestType); // wyszukanie dla podanego Requesta odpowiedniego handlera 
            if(rh == null) {
               // wyslij response ze unsupported operation
               Response r = new Response(StatusCode.NOT_IMPLEMENTED);
               r.write(out);
               System.err.println("Unsupported method for: "+requestLine);
               incomming.close();
               return;
            }
            else {
               rh = rh.copy(); // sklonowanie obiektu RequestHandlera
               Request r = new Request(requestLine);  
               if(r.getMethod() == null) { // nie prawidlowa wersja protokolu
                  Response rs = new Response(StatusCode.NOT_SUPPORTED);
                  rs.write(out);
                  System.err.println("Wrong protocol: "+requestLine);
                  incomming.close();
                  return;
               }
               r.readHeaders(incomming.getInputStream()); // UWAGA w strumieniu nie ma juz request line - nie nalezy oczekiwac ze to sie tam jeszcze znajduje
               // teraz r jest w pelni wypelniony, wskaznik ustawiony na pierwszy bajt danych
               rh.setSocket(incomming); // socket przekazywany do oddzielnego watku
               rh.setRequest(r);// przekazywanie wypelnione requesta
               System.out.println("Received request: \n"+r);
               rh.run(); // przekazanie obslugi do okreslonego handlera.
            }
         } catch (IOException e) {
            e.printStackTrace(); 
            return; // stracono polaczenie w trakcie czytania naglowka
         } finally {
            try {
               incomming.close();
            } catch (IOException e) {
               // TODO Auto-generated catch block
               e.printStackTrace();
            }
         }
       }
      /**
       * Przeglada request line i zwraca pierwszy wyraz
       * requestu (rodzaj requestu).
       * 
       * @return pierwszy wyraz 
       */
      private String getRequestType(String headLine) {
         StringTokenizer stkn = new StringTokenizer(headLine);
         return stkn.nextToken();// zwraca pierwszy wyraz
      }
   }
}
